package lion.framework.core.bean;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import lion.framework.core.bean.anno.Inject;
import lion.framework.core.bean.anno.RegisteredBean;

/**
 * 创建与保存Bean的容器，每一个beanFactory实例有一个contextName，
 *
 * <pre>
 * time   2013 2013年12月3日 下午3:59:47
 * mail   hl_0074@sina.com
 * </pre>
 *
 * @author lion
 */
public class BeanFactory {

	public static final String PLATFORM_BEAN_FACTORY = "lion.framework.beanfactory";

	private static Map<String, BeanFactory> beanFactoryMap = new HashMap<>();

	private Map<String, Object> beanMap = new HashMap<>();
	private String contextName;

	private BeanFactory(String contextName) {

		this.contextName = contextName;
	}

	/**
	 * get the bean from registed @Bean
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String name) {

		T t = (T) beanMap.get(name);
		if (t == null) {
			Class<?> clazz = RegisteredBean.get(name);
			t = (T) get(name, clazz);
		}

		return t;
	}

	/**
	 * get or create a bean with parameters name and class name
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String name, String className) {

		Class<T> c;
		try {
			c = (Class<T>) Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return null;
		}
		return get(className, c);
	}

	/**
	 * get or create a bean from the beanfactory with the specified class
	 */
	public <T> T get(String name, Class<T> clazz) {

		return get(name, clazz, new Object[0]);
	}

	/**
	 * get or create a bean with parameters name,class,and constructor
	 * parameters
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String name, Class<T> clazz, Object... params) {

		T t = (T) beanMap.get(name);
		if (t == null) {

			t = create(clazz, params);
			if (t != null) {
				beanMap.put(name, t);
				// inject
				Field[] fields = clazz.getDeclaredFields();
				for (Field field : fields) {
					if (field.isAnnotationPresent(Inject.class)) {
						injectField(t, field);
					}
				}
			}
		}

		return t;
	}

	/**
	 * find the name regiestered,from inject value,field value,className, if all
	 * of them not found,it will create the bean directly
	 */
	private void injectField(Object target, Field field) {

		Class<?> n = field.getType();
		// inject
		Inject inject = field.getAnnotation(Inject.class);
		String name = inject.value();
		if (StringUtils.isBlank(name)) {
			name = n.getName();
		}

		Class<?> clazz = RegisteredBean.get(name);
		if (clazz == null) {
			clazz = n;
		}

		Object value = get(name, clazz);

		try {
			field.setAccessible(true);
			field.set(target, value);
		} catch (IllegalArgumentException | IllegalAccessException e) {
			e.printStackTrace();
		}
	}

	public String getContextName() {

		return contextName;
	}

	/**
	 * get or create a bean factory with contextName
	 */
	public static BeanFactory getBeanFactory(String contextName) {

		BeanFactory bf = beanFactoryMap.get(contextName);
		if (bf == null) {
			bf = new BeanFactory(contextName);
			beanFactoryMap.put(contextName, bf);
		}
		return bf;
	}

	/**
	 * create bean with given class
	 */
	public static <T> T create(Class<T> clazz) {

		return create(clazz, new Object[0]);
	}

	/**
	 * create bean with given class name
	 */
	public static <T> T create(String clazz) {

		return create(clazz, new Object[0]);
	}

	/**
	 * create bean with classname and given params
	 */
	@SuppressWarnings("unchecked")
	public static <T> T create(String className, Object... params) {

		Class<T> c;
		try {
			c = (Class<T>) Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return null;
		}
		return create(c, params);
	}

	/**
	 * create bean with the class and given params
	 */
	public static <T> T create(Class<T> clazz, Object... params) {

		if (clazz == null) { return null; }
		return createTarget(clazz, params);
	}

	private static <T> T createTarget(Class<T> clazz, Object... params) {

		if (clazz.isPrimitive() || clazz.isArray() || clazz.isEnum() || clazz.isInterface()) { return null; }

		if (params.length == 0) {
			try {
				return clazz.newInstance();
			} catch (Exception e) {
				throw new RuntimeException("no default constructor defined in class:" + clazz.getName(), e);
			}
		}

		Class<?>[] clazzess = new Class<?>[params.length];
		for (int i = 0; i < params.length; i++) {
			clazzess[i] = params[i].getClass();
		}
		Constructor<T> con = null;
		try {
			con = clazz.getDeclaredConstructor(clazzess);
		} catch (Exception e) {
			throw new RuntimeException("there is no constructor defined in class " + clazz.getName() + " with arguments type:" + Arrays.toString(clazzess), e);
		}
		try {
			con.setAccessible(true);
			return con.newInstance(params);
		} catch (Exception e) {
			throw new RuntimeException("exception on create class:" + clazz.getName(), e);
		}
	}
}
